{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit Picture;

interface

uses AVRenderer, Canvas, AHMTypes, Colour, Graphic;

const
  PAN_ZOOM_FACTOR = 0.85; // Amount of image to zoom in/out of
  FADE_DURATION = 1.5; // Duration of fade over effect in seconds
  OFF_PAN_ZOOM_DURATION = 30; // Duration of pan/zoom when slide time = 0

type
  TAHMAnimationMode = (amStarted, amFadeBegin, amFading, amPanAndZoom, amFinished);

  TAHMPictureRenderer = class(TAHMAVRenderer)
  private
    FUsePanAndZoom: Boolean;
    FUseFading: Boolean;
    FShowBorders: Boolean;
    FDisplayDuration: Integer;
    FInfiniteDuration: Boolean;
    FAnimationMode: TAHMAnimationMode;
    FDisplayTime: Single;
    FCurrentImage: TAHMGraphic;
    FNextImage: TAHMGraphic;
    FCurrentGamma: TAHMCanvasColour;
    FNextGamma: TAHMCanvasColour;
    FCurrentRect: TAHMRectF;
    FNextRect: TAHMRectF;
    FPanZoomRect: TAHMRectF;
    FPanZoomVX: Single;
    FPanZoomVY: Single;
    FPanZoomVW: Single;
    FPanZoomVH: Single;
    FCurrentFade: Single;
  protected
    procedure LoadCurrentImage;
    procedure RetrievePreferences;
    procedure InitialisePicture;
    procedure UpdateAnimation;
  public
    constructor Create;
    destructor Destroy; override;
    procedure ReleaseResources; override;
    procedure Render(Rect: TAHMRectF; Shade: TAHMCanvasShade; Gamma: TAHMCanvasColour;
                     XScale, YScale: Single; XFlip, YFlip: Boolean); override;
    function Play: Boolean; override;
    function Pause: Boolean; override;
    function Stop: Boolean; override;
    function SkipForward: Boolean; override;
    function SkipBackward: Boolean; override;
  end;

implementation

uses Math, SysUtils, ApplicationManager, Playlist, DirectXHelper, Timing, Screen;

constructor TAHMPictureRenderer.Create;
begin
  inherited Create;

  FCurrentImage := TAHMGraphic.Create;
  FNextImage := TAHMGraphic.Create;
  FCurrentGamma := TAHMCanvasColour.CreateARGB(255, 255, 255, 255);
  FNextGamma := TAHMCanvasColour.CreateARGB(255, 255, 255, 255);

  FAnimationMode := amStarted;
end;

destructor TAHMPictureRenderer.Destroy;
begin
  // Set image references to nil so we don't try to release them again in
  // inherited destructors ReleaseResources method
  FreeAndNil(FCurrentImage);
  FreeAndNil(FNextImage);

  FCurrentGamma.Free;
  FNextGamma.Free;

  inherited Destroy;
end;

procedure TAHMPictureRenderer.LoadCurrentImage;
var
  Item: TAHMMediaItem;
begin
  // Get current image details from playlist
  Item := Playlist.CurrentItem;
  if not Assigned(Item) then Exit;

  // Set slideshow preferences
  RetrievePreferences;

  // Load appropriate next image
  FNextImage.Invalidate;
  FNextImage.LoadFromFile(Item.URL);
end;

procedure TAHMPictureRenderer.RetrievePreferences;
begin
  // Set slideshow preferences
  with ApplicationManagerInstance.PreferencesManager do
  begin
    FDisplayDuration := SlideshowDelay;
    FUsePanAndZoom := SlideshowPanZoom;
    FUseFading := SlideshowFading;
    FShowBorders := SlideshowBorders;
  end;

  // If slideshow duration is zero, we'll animate over an extended period
  FInfiniteDuration := (FDisplayDuration = 0);
  if FInfiniteDuration then FDisplayDuration := OFF_PAN_ZOOM_DURATION;
end;

procedure TAHMPictureRenderer.InitialisePicture;
var
  ImgAspect, ScaledWidth, ScaledHeight: Single;
  PZStartRect, PZFinishRect, TempRect: TAHMRectF;
begin
  // Check new image has loaded okay
  if (FNextImage.ImageWidth > 0) and (FNextImage.ImageHeight > 0) then
  begin
    // Calculate screen and image aspect ratio
    ImgAspect := FNextImage.ImageWidth / FNextImage.ImageHeight;

    // Scale image to match screen and image aspect ratios
    if ImgAspect < ScreenInstance.AspectRatio then
    begin
      ScaledWidth := ScreenInstance.PhysicalHeight * ImgAspect;
      ScaledHeight := ScreenInstance.PhysicalHeight;
    end
    else
    begin
      ScaledWidth := ScreenInstance.PhysicalWidth;
      ScaledHeight := ScreenInstance.PhysicalWidth / ImgAspect;
    end;

    // Setup display rectangle for full image
    PZStartRect := FNextImage.ImageRect;

    // If we're using Pan & Zoom then choose a subset rectangle of the image
    if FUsePanAndZoom then
    begin
      PZFinishRect.Width := PAN_ZOOM_FACTOR;
      PZFinishRect.Height := PAN_ZOOM_FACTOR;
      PZFinishRect.X := Random * (1 - PAN_ZOOM_FACTOR);
      PZFinishRect.Y := Random * (1 - PAN_ZOOM_FACTOR);

      // Choose a random direction for zoom - in or out
      if (Random(1000) > 500) then
      begin
        // Zooming out so swap display & target rectangles
        TempRect := PZStartRect;
        PZStartRect := PZFinishRect;
        PZFinishRect := TempRect;
      end;

      // Calculate pan/zoom pixel speeds
      FPanZoomVX := (PZFinishRect.X - PZStartRect.X) / FDisplayDuration;
      FPanZoomVY := (PZFinishRect.Y - PZStartRect.Y) / FDisplayDuration;
      FPanZoomVW := (PZFinishRect.Width - PZStartRect.Width) / FDisplayDuration;
      FPanZoomVH := (PZFinishRect.Height - PZStartRect.Height) / FDisplayDuration;
    end
    else
    begin
      // No pan/zoom effect required, so just zero pixel speeds
      FPanZoomVX := 0.0;
      FPanZoomVY := 0.0;
      FPanZoomVW := 0.0;
      FPanZoomVH := 0.0;
    end;

    // Set required size for image so we don't have to deal with huge textures
    FNextImage.ResizeImage(Round(ScaledWidth), Round(ScaledHeight));

    // Set initial display position for our new image
    FPanZoomRect := PZStartRect;
    FNextRect := FPanZoomRect;

    // Now we want to fade in our new image
    FAnimationMode := amFadeBegin;
  end;
end;

procedure TAHMPictureRenderer.UpdateAnimation;
var
  TempGraphic: TAHMGraphic;
  FrameTime: Integer;
begin
  // Get elapsed frame time to regulate picture animations
  FrameTime := TimerInstance.CurrentFrameTime;
  if FrameTime = 0 then Exit;

  // What's the current animation state?
  case FAnimationMode of
    amStarted:
    begin
      // Invalidate current image (from previous slideshow)
      FCurrentImage.Invalidate;

      // Load & initialise first image
      LoadCurrentImage;
      InitialisePicture;

      // Hide both next & current images
      FCurrentGamma.InternalAlpha := 0;
      FNextGamma.InternalAlpha := 0;
    end;
    amFadeBegin:
    begin
      // Don't include this frame when timing fade animation
      FAnimationMode := amFading;
      FDisplayTime := 0.0;
      Exit;
    end;
    amFading:
    begin
      // Skip to last animation fade step if not required
      if not FUseFading then FCurrentFade := 1.0
                        else FCurrentFade := FDisplayTime / FADE_DURATION;
      if FCurrentFade > 1.0 then FCurrentFade := 1.0; // prevent range error

      // Update gamma values to fade current image out & new image in
      FNextGamma.InternalAlpha := Round(255 * FCurrentFade);
      FCurrentGamma.InternalAlpha := 255 - FNextGamma.InternalAlpha;

      // Have we finished fading images yet?
      if FCurrentFade >= 1.0 then
      begin
        // Swap next/current images
        TempGraphic := FNextImage;
        FNextImage := FCurrentImage;
        FCurrentImage := TempGraphic;

        // Hide next (now current) image and show current image completely
        FCurrentGamma.InternalAlpha := 255;
        FNextGamma.InternalAlpha := 0;

        // Copy display position for next (now current) image
        FCurrentRect := FNextRect;

        // Now we want to pan/zoom the new (now current) image
        FAnimationMode := amPanAndZoom;
        FDisplayTime := 0.0;
      end;
    end;
    amPanAndZoom:
    begin
      // Update pan/zoom position whilst slideshow playing
      if (MediaState = msPlaying) and (FDisplayTime < FDisplayDuration) then
        with FPanZoomRect do
        begin
          X := X + (FPanZoomVX * FrameTime / 1000);
          Y := Y + (FPanZoomVY * FrameTime / 1000);
          Width := Width + (FPanZoomVW * FrameTime / 1000);
          Height := Height + (FPanZoomVH * FrameTime / 1000);
        end;
      FCurrentRect := FPanZoomRect;

      // Have we shown image for long enough yet?
      if FDisplayTime > FDisplayDuration then
      begin
        // If infinite display duration then repeat last frame
        if FInfiniteDuration then
          FDisplayTime := FDisplayDuration
        else
        begin
          // Signal that it's time to load next image
          SignalMediaEvent(msigNext);
        end;
      end;
    end;
  end;

  // Increment current image display duration & animation whilst playing
  if MediaState = msPlaying then
    if (FAnimationMode = amFading) or (FDisplayDuration > 0) then
      FDisplayTime := FDisplayTime + (FrameTime / 1000);
end;

procedure TAHMPictureRenderer.ReleaseResources;
begin
  // Release stored images
  if Assigned(FNextImage) then FNextImage.Invalidate;
  if Assigned(FCurrentImage) then FCurrentImage.Invalidate;
end;

procedure TAHMPictureRenderer.Render(Rect: TAHMRectF; Shade: TAHMCanvasShade;
                                     Gamma: TAHMCanvasColour; XScale, YScale: Single;
                                     XFlip, YFlip: Boolean);

  function CenterAndClipImage(Image: TAHMGraphic): TAHMRectF;
  begin
    Result.X := Rect.X;
    Result.Y := Rect.Y;
    Result.Width := Rect.Width;
    Result.Height := Rect.Height;

    // If scaled image width is smaller than render rect then center horizontally
    if Image.Width < Rect.Width then
    begin
      Result.X := Result.X + ((Rect.Width - Image.Width) / 2);
      Result.Width := Image.Width;
    end;
    // If scaled image height is smaller than render rect then center vertically
    if Image.Height < Rect.Height then
    begin
      Result.Y := Result.Y + ((Rect.Height - Image.Height) / 2);
      Result.Height := Image.Height;
    end;
  end;
var
  DrawRect: TAHMRectF;
begin
  inherited;

  if MediaState = msStopped then Exit;

  // Update animations if we haven't already done so this frame
  if UpdateRequired then UpdateAnimation;

  // Render current/next images
  if FCurrentImage.Loaded then
  begin
    DrawRect := CenterAndClipImage(FCurrentImage);
    FCurrentImage.DrawGraphic(FCurrentRect, DrawRect, Shade, FCurrentGamma, XScale, YScale, XFlip, YFlip);
  end;
  if FNextImage.Loaded then
  begin
    DrawRect := CenterAndClipImage(FNextImage);
    FNextImage.DrawGraphic(FNextRect, DrawRect, Shade, FNextGamma, XScale, YScale, XFlip, YFlip);
  end;
end;

function TAHMPictureRenderer.Play: Boolean;
begin
  // Reinitialise slideshow if it was previously stopped
  if FAnimationMode = amFinished then FAnimationMode := amStarted;
  Result := True;
end;

function TAHMPictureRenderer.Pause: Boolean;
begin
  Result := True;
end;

function TAHMPictureRenderer.Stop: Boolean;
begin
  FAnimationMode := amFinished;
  Result := True;
end;

function TAHMPictureRenderer.SkipForward: Boolean;
begin
  // Load & initialise new image
  LoadCurrentImage;
  InitialisePicture;
  Result := True;
end;

function TAHMPictureRenderer.SkipBackward: Boolean;
begin
  // Load & initialise new image
  LoadCurrentImage;
  InitialisePicture;
  Result := True;
end;


end.
